package com.zxx.dis.lock;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

@Component
public class RedisReentrantLock implements Lock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final String LOCK_PREFIX = "reentrant_lock:";

    private String lockName;
    private String threadId;
    ThreadLocal<String> uuidThreadLocal = ThreadLocal.withInitial(() -> UUID.randomUUID().toString());
    public RedisReentrantLock() {
        this.lockName = "lock";
//        this.threadId = Thread.currentThread().getId() + ":" + UUID.randomUUID();
    }

    @Override
    public void lock() {
        threadId = String.valueOf(UUID.randomUUID());
        while (!tryLock()) {
            try {
                System.out.println(threadId);
                Thread.sleep(100); // 简单重试
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return;
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        while (!tryLock()) {
            if (Thread.currentThread().isInterrupted()) {
                throw new InterruptedException();
            }
            Thread.sleep(50);
        }
    }

    @Override
    public boolean tryLock() {
        String lockKey = lockName;

        String script = "if redis.call('exists', KEYS[1]) == 0 then " +
                "redis.call('hset', KEYS[1], ARGV[1], 1); " +
                "return 1; " +
                "elseif redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
                "redis.call('hincrby', KEYS[1], ARGV[1], 1); " +
                "return 1; " +
                "else " +
                "return 0; " +
                "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);

        Long result = stringRedisTemplate.execute(redisScript, Arrays.asList(lockKey), uuidThreadLocal.get(), String.valueOf(30 * 1000));
        return result != null && result == 1;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        long startTime = System.currentTimeMillis();
        long timeout = unit.toMillis(time);

        while (!tryLock()) {
            if (System.currentTimeMillis() - startTime > timeout) {
                return false;
            }
            if (Thread.currentThread().isInterrupted()) {
                throw new InterruptedException();
            }
            Thread.sleep(100);
        }
        return true;
    }

    @Override
    public void unlock() {
        String lockKey = lockName;

        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then " +
                "return 0; " +
                "else " +
                "local count = redis.call('hincrby', KEYS[1], ARGV[1], -1); " +
                "if count > 0 then " +
                "return 1; " +
                "else " +
                "redis.call('del', KEYS[1]); " +
                "return 1; " +
                "end " +
                "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);

        stringRedisTemplate.execute(redisScript, Arrays.asList(lockKey), uuidThreadLocal.get(), String.valueOf(30 * 1000));
    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException("Redis lock does not support conditions");
    }
}